#!/usr/bin/env python
#-------------------------------------------------------------------------------
# Name:        plot_tiled.py  (with Qt GUI)
# Purpose:     Make a tiled plot (using subplots) from transects (profiles) 
#              in csv files 
#
# Author:      Chris Harding, ISU
#-------------------------------------------------------------------------------

# changes:
# June 13, 2011 - made main() so plot_tiled.py can be imported and it's main called from a GUI script
# June 14 - made log to be a true log 10 transform instead of just semilogy plotting 
# June 15 - added Qt GUI
# June 26 - added navbar and radiobuttons but commented out the radiobuttons for the 6-27
#           meeting with Sharon
# July 3 - log10 in now natural log (ln)
# July 6 - center point coords are read from a csv file called center_point_coordinates.csv
#          withing the csv file folder. The plots are now centered around the
#          x/y of the data csv file that's closest to this center point (incl. gey line)
#          Needs lots of testing!!!
#        - changed lists of defaults from tuples to lists as 1-element tuples are 
#          "collapsed" unless at comma is use t = ("a",)
#        - added a bool link_subplots, True links all subplots together 
 

import os
import sys
import os.path
import csv
import math
import numpy as np

import matplotlib as mpl
try:
    mpl.use('Qt4Agg')
except:
    sys("Can't use Matplotlib's Qt4Agg, install PyQt4 (binary installers: www.riverbankcomputing.co.uk/software/pyqt/download ")

from PyQt4 import QtCore

# Python Qt4 bindings for GUI objects
from PyQt4 import QtGui


# Matplotlib Figure object
from matplotlib.figure import Figure
# import the Qt4Agg FigureCanvas object, that binds Figure to
# Qt4Agg backend. It also inherits from QWidget
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
# import the NavigationToolbar Qt4Agg widget
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QTAgg as NavigationToolbar
import matplotlib.font_manager


#import matplotlib.pyplot as plt

def smooth(data, window_len=11, window='hanning'): # from http://www.scipy.org/Cookbook/SignalSmooth
    """smooth the data using a window with requested size.
    
    This method is based on the convolution of a scaled window with the signal.
    The signal is prepared by introducing reflected copies of the signal 
    (with the window size) in both ends so that transient parts are minimized
    in the begining and end part of the output signal.
    
    input:
        x: the input signal 
        window_len: the dimension of the smoothing window; should be an odd integer
        window: the type of window from 'flat', 'hanning', 'hamming', 'bartlett', 'blackman'
            flat window will produce a moving average smoothing.

    output:
        the smoothed signal
        
    example:

    t=linspace(-2,2,0.1)
    x=sin(t)+randn(len(t))*0.1
    y=smooth(x)
    
    see also: 
    
    numpy.hanning, numpy.hamming, numpy.bartlett, numpy.blackman, numpy.convolve
    scipy.signal.lfilter
 
    TODO: the window parameter could be the window itself if an array instead of a string   
    """
    if data.ndim != 1:
        raise ValueError, "smooth only accepts 1 dimension arrays."

    if data.size < window_len:
        raise ValueError, "Input vector needs to be bigger than window size."

    if window_len<3:
        raise ValueError, "Window size must be >= 3"

    if not window in ['flat', 'hanning', 'hamming', 'bartlett', 'blackman']:
        raise ValueError, "Window must be one of: 'flat', 'hanning', 'hamming', 'bartlett', 'blackman'"

    s=np.r_[2*data[0]-data[window_len-1::-1], data ,2*data[-1]-data[-1:-window_len:-1]] # 
    #print(len(s))
    if window == 'flat': #moving average
        w=np.ones(window_len,'d')
    else:
        w=eval('np.'+window+'(window_len)')

    y=np.convolve(w/w.sum(),s,mode='same')
    r = y[window_len:-window_len+1]
    
    return r # return smoothed
    
# hill shape class - stores original M (distance in m along profile, x direction) and
# Z (elevation of each point, y direction) as read from csv file
class Hillshape:
    def __init__(self): # default constructor (no args)
        self.filename = "None"
        self.Z = [] # elevation values (horizontal, in m)
        self.x = [] # x coordinate
        self.y = [] # y coordinate
        self.num_lines = 0 # number of lines 
        self.median_ratio = -1 # where from 0 to 1 is the center point ("middle")?

        # used to make arrays later, once we know the length
        self.window_sizes = [1] # list of window sizes for filtering
        self.z = [] # list of arrays (unfiltered = filtered with size 1
        self.m = None
        self.min_max_y = [] # list of min/max per window size (1=unsmoothed)
    
    # assumes 2 columns
    def read_csv_file(self, filename): 
        reader = csv.reader(open(filename, "rb"))
        for row in reader: # row contains a list of strings
            #print row
            z = float(row[2])  # 3. row, convert string to float, seems to only be precise up to 3 digits! 
            self.Z.append(z)
            
            pp = row[0].find('.')
            row[0] = row[0][:pp+3]  # only keep 2 digits
            self.x.append(float(row[0]))

            pp = row[1].find('.')
            row[1] = row[1][:pp+3]
            self.y.append(float(row[1]))
        
        #a z value of 0 probably means Nodata, delete this line
        for i,z in enumerate(self.Z): 
            if z == 0: 
                del self.Z[i]; del self.x[i]; del self.y[i]
                print filename, row, "ignored a 0 value (warning only)" 
        
        self.filename = filename
        self.num_lines = len(self.Z)
        print "read in", filename, self.num_lines, "samples, pixel min/max:", min(self.Z), max(self.Z), "range:", max(self.Z)-min(self.Z) 
        return True
    
    # add smoothed versions of z to self.z[]
    def calc_smoothed_versions_of_z(self, window_sizes, window_type='hanning'):
        assert len(self.z) == 1, "self.z must only have 1 array"
        self.window_sizes = [1] + window_sizes # list of window sizes, 1 means unfiltered
        self.window_type = window_type
        self.min_max_y=[]
        for ws in window_sizes:
            if ws == 1: # no smoothing for size 1 (unsmoothed)
                smoothed = self.z[0] # fake, for getting full list of min/max
            else: 
                smoothed = smooth(self.z[0], ws, window_type)
                self.z.append(smoothed)
            mn = min(smoothed)
            mx = max(smoothed)
            self.min_max_y.append((mn,mx))
            
        print "smoothed with", window_type, window_sizes, self.min_max_y
    
def prepare_data(csv_folder, csv_file_name, csv_center_point_file, time_steps, 
                 bearings, window_sizes, filter_type, transform, gp=None):   
    hills = {} # dict of Hillshape objects, key is filename
    max_length = -1 # longest profile
    min_Z = 256
    max_Z = -1
    num_hills = 0 # incr as read in
    
    # try to read in x/y of center point
    try:
        cp_file = open(csv_center_point_file)
    except Exception, exp:
        sys.exit("Error: can't open " + csv_center_point_file + " - " + str(exp))
        
    l = cp_file.read() # read in first and only line
    l = l.replace(" ","") # remove spaces
    l = l.replace("\n","") # and newlines
    x, y = l.split(',') # split at comma
    center_point_coordinates = (float(x), float(y))
    print "center point coordinates are", x,y
    
    # read in profiles from .csv files
    for ts in time_steps:
        for br in bearings:
            f = csv_folder + os.sep + csv_file_name + "_" + ts + \
                "_" + br + ".csv" # assemble full path/filename for each csv file
            
            #each .csv files contains 3 columns (x,y,Z) but no header!
            h = Hillshape() # construct new profile
            h.read_csv_file(f)
            fkey = os.path.basename(f)
            #print f, fkey
            
            hills[fkey] = h  # add profile to dict with base filename as key
            
            # x/y coords of first sample
            x0 = h.x[0]
            y0 = h.y[0]
            
            # dist of center point to first sample
            cp_delta_x = center_point_coordinates[0] - x0
            cp_delta_y = center_point_coordinates[1] - y0
            dist2cp = math.hypot(cp_delta_x, cp_delta_y)
            
            dist_val = []   # list for distance, value pairs
            # get euclidian distance from x0/y0 (needed for sorting by distance)
            for i in range(0,len(h.Z)):
                
                # x and y offsets of current sample from the first sample
                delta_x = h.x[i] - x0
                delta_y = h.y[i] - y0
                
                dist = math.hypot(delta_x, delta_y)  # 2D distance to first sample
                dist_val.append((dist, h.Z[i])) # value of current sample
                #print i, h.x[i], h.y[i], dist, "-", dist2cp,  center_point_coordinates
            
            #sort by distance 
            dist_val.sort()
            dists = []
            vals = []
            for d,v in dist_val:
                dists.append(d)
                vals.append(v)
                #print d,v
                
            #Store in numpy arrays
            h.m = np.array(dists) # needed as x values for plotting
            vl = np.array(vals) # make array from values
            
            # find the index of the sample which the distance to the first sample
            # is closest to dist2cp (the center point's distance to first sample)
            # and use it to get a ratio that we'll treat as the "middle" of the plot
            index_of_median = np.searchsorted(h.m, dist2cp)
            print "median sample's index is", index_of_median, "of", len(h.Z)-1,"closest dist", h.m[index_of_median], "true dist", dist2cp, "value",vl[index_of_median], "ratio",
            median_ratio = dist2cp / dists[-1] # at which % (0 to 1) is the median ?
            median_ratio = h.m[index_of_median] / dists[-1] # at which % (0 to 1) is the median ?
            print median_ratio
            h.median_ratio = median_ratio # store with profile

            # log transform vl
            if transform == "log": vl = np.log(vl)
            mn = min(vl); mx = max(vl) # min/max of values
            #print mn, mx
            h.z.append(vl) # z[0] for unfiltered z data
            
            # get longest profile (dist[last] has longest distance) and global min/max for z
            if dists[-1] > max_length: max_length = dists[-1]
            if mn < min_Z: min_Z = mn
            if mx > max_Z: max_Z = mx
            
            # Filtering - add a list of smoothed (filtered) profiles with different sizes to z
            # also calcs list of min/max tuples per window size
            h.calc_smoothed_versions_of_z(window_sizes, filter_type)

            num_hills += 1
        
    #plt.ylim(min_Z, min_Z)
    print "read in ",num_hills, "profiles, longest:", "%.2f" % max_length, "meters, min pixel value", min_Z,\
          "max pixel value", max_Z
    
    # Store properties in dict
    hills["max_length"] = max_length # store value of widest (longest) profile
    hills["min_Z"] = min_Z
    hills["max_Z"] = max_Z
    hills["csv_folder"] = csv_folder
    hills["csv_file_name"] = csv_file_name
    hills["time_steps"] = time_steps
    hills["bearings"] = bearings
    hills["window_sizes"] = window_sizes
    hills["filter_type"] = filter_type
    hills["transform"] = transform
   
    
    return hills # dict of hills

def plot(profiles,  winsize=1, link_subplots=False, sub_plot_setup="3 x 3", 
         application_window=None, gp=None):

    subplot_width_inches = 3.66
    subplot_height_inches = 2.83
    mpl.interactive(False)  # start non-interactive
    bg = (1.0,1.0,1.0 ) # background for plot figure
    num_splots = sub_plot_setup.replace(' ','').split('x') # 
    num_sub_plots_x = int(num_splots[0]) # number of subplots in x
    num_sub_plots_y = int(num_splots[1]) # number of subplots in y
    
    
    # make pagesize tuple dependent on subplot setup    
    paper_size = (num_sub_plots_x * subplot_width_inches, num_sub_plots_y * subplot_height_inches) 
    paper_size = (11, 8.5)
    fig = Figure(facecolor='w', edgecolor='k', figsize = paper_size)
    
    #fig.set_size_inches(paper_size)
    #fig.set_frameon(False)
    #fig.set_edgecolor(0.75)
    #fig.set_facecolor('white')
    #fig.set_dpi = 300
    
    
    #http://matplotlib.sourceforge.net/users/customizing.html#the-matplotlibrc-file
    #figure.figsize   : 8, 6    # figure size in inches
    #figure.dpi       : 80      # figure dots per inch
    #figure.facecolor : 0.75    # figure facecolor; 0.75 is scalar gray
    #figure.edgecolor : white   # figure edgecolor

    # The figure subplot parameters.  All dimensions are fraction of the
    # figure width or height
    #figure.subplot.left    : 0.125  # the left side of the subplots of the figure
    #figure.subplot.right   : 0.9    # the right side of the subplots of the figure
    #figure.subplot.bottom  : 0.1    # the bottom of the subplots of the figure
    #figure.subplot.top     : 0.9    # the top of the subplots of the figure
    #figure.subplot.wspace  : 0.2    # the amount of width reserved for blank space between subplots
    #figure.subplot.hspace  : 0.2    # the amount of height reserved for white space between subplots
    #cairo.format      : png      # png, ps, pdf, svg
    mpl.rcParams['cairo.format'] = 'pdf'
    #savefig.dpi       : 100      # figure dots per inch
    mpl.rcParams["savefig.dpi"] = 300
    #savefig.facecolor : white    # figure facecolor when saving
    #savefig.edgecolor : white    # figure edgecolor when saving
    #savefig.extension : auto     # what extension to use for savefig('foo'), or 'auto'
    mpl.rcParams['font.size'] = 7 # 
    mpl.rcParams['agg.path.chunksize'] = 10000
    #pdf.fonttype       : 3         # Output Type 3 (Type3) or Type 42 (TrueType)
    mpl.rcParams['pdf.fonttype'] = 42
    
    # make a colormap with  number of timessteps colors
    #colormap = plt.get_cmap("spectral_r", len(time_steps)+1)  # reversed spectral colormap +1 to skip grey
    
    ## find min/max for this window size - used for ylim later
    #max_Z = -1; min_Z = 256 # init min/max for Z of this window size    
    #for i,br in enumerate(bearings):
        #for j,ts in enumerate(time_steps):
            #fkey = csv_file_name + "_" + ts + "_" + br + ".csv" # filename as key
            #h = profiles[fkey] # get profile with that name
            ##print fkey,h
            ##if gp: gp.AddMessage("  plotting " + fkey + ", winsize " + str(winsize))
            
            ## get max z values and min z value of all profiles for this window size
            #mn = h.z[winsize_idx].min() # not sure if that's better than the standard min() ???
            #mx = h.z[winsize_idx].max()
            #if mx > max_Z: max_Z = mx
            #if mn < min_Z: min_Z = mn
    
    
    max_length = profiles["max_length"] # store value of widest (longest) profile
    min_Z = profiles["min_Z"]
    max_Z = profiles["max_Z"]
    csv_folder = profiles["csv_folder"]
    csv_file_name = profiles["csv_file_name"]
    time_steps = profiles["time_steps"]
    bearings = profiles["bearings"]
    window_sizes = profiles["window_sizes"]
    filter_type = profiles["filter_type"]
    transform = profiles["transform"]
   

    try:
        winsize_idx = window_sizes.index(winsize) # find the index of requested size within the list of available sizes 
    except:
        return("Error - requested winsize " + str(winsize) + "not available")
    print "\nplotted", csv_file_name, "for winsize", winsize
    msg = "plotted " + csv_file_name + " for window size " + str(winsize)
    #application_window.statusBar().showMessage(msg)
    application_window.status_text.setText(msg)
    if gp: gp.AddMessage(msg)
    
    # go through all bearings, each gets a new subuplot
    for i,br in enumerate(bearings): # br is a string
        if i >= num_sub_plots_x * num_sub_plots_y: break # skip if needed
        #print "bearing", br, "in subplot", i+1, "of", num_sub_plots_y, "x", num_sub_plots_x
        if i == 0: # first subplot axis?
            ax1 = fig.add_subplot(num_sub_plots_y, num_sub_plots_x, i+1, axisbg=bg) # i is 0 based, must be 1 based
            ax = ax1
        else:
            if link_subplots:
                ax = fig.add_subplot(num_sub_plots_y, num_sub_plots_x, i+1, axisbg=bg, 
                                 sharex=ax1, sharey=ax1) # share x and y with first subplot
            else: ax = fig.add_subplot(num_sub_plots_y, num_sub_plots_x, i+1, axisbg=bg)
        #is_first_row, is_first_col, is_last_row, is_last_col
        #if not ax.is_first_row(): 
        
        median_ratio = -1  # median ratio (center point) for all lines in this bearing
        
        # each timestep gets a line within each subplot (new color)
        for tscnt,ts in enumerate(time_steps): 
            fkey = csv_file_name + "_" + ts + "_" + br + ".csv" # filename as key 
            h = profiles[fkey] # get profiles with that name
            median_ratio = h.median_ratio  
            
            # shift m values so that median (i.e. center point) is at 0
            m_copy = h.m.copy()
            m_ctr = m_copy - h.m[-1] * median_ratio # centere array of x at ratio
            
            # plot with plot (linear) and keep plot handle
            ax.plot(m_ctr, h.z[winsize_idx], 
                    #color=colormap(tscnt+1), # colormap is a function +1 to skip grey
                    alpha=0.95,
                    label=ts) #  
        # end timestep loop
        
        # configure subplot 
        ax.grid()
        title = " bearing: " + br + " degr."
        ax.set_title(title , fontsize=9, va="baseline")
        ax.set_autoscale_on(False)

        # the x lims seem good without explicit settins
        # graphically "flip" plot along x for bearings from 90 to 180
        # seems to only work when link_subplots is False ??
        if 0 <= int(br) <= 90:  # br is a string!
            #ax.set_xlim(-max_length * median_ratio, max_length * median_ratio)
            pass
        else:
            #ax.set_xlim(max_length * median_ratio, -max_length * median_ratio)
            ax.set_xlim(ax.get_xlim()[::-1]) # flip x
            
            
             
        ax.set_ylim(min_Z, max_Z) # set y limits of all subplots to global min/max 
        ax.vlines(h.median_ratio, min_Z, max_Z, color=(0.5,0.5,0.5), linewidth=1.5) # grey vertical center line
        
    # end bearing loop

    # legend
    lines = ax.get_lines() # grab plotted lines from last axis, 
    labels = [l.get_label() for l in lines] # get their labels
    legendfont = matplotlib.font_manager.FontProperties(size=8) 
    fig.legend(lines, labels,"upper right", fancybox=True, prop=legendfont, 
                  #ncol=1, 
                  bbox_to_anchor = (0.98, 0.995),
                  )
    fig.subplots_adjust(left = 0.05, bottom=None, right=0.98, top=None, wspace=0.1, hspace=0.25)
    
    if winsize == 1: # unsmoothed
        title_text = "NIR transects for " + csv_file_name + " - unfiltered"
        savefigname = csv_folder + os.sep + csv_file_name  # name of pdf
    else: # smoothed with winsize 
        title_text = "NIR transects for " + csv_file_name + " - " + \
                   filter_type + " filtered with window size " + str(winsize)
        savefigname = csv_folder + os.sep + csv_file_name +"_" + str(winsize)  # name of pdf
    if transform == "log": title_text += ", log transformed" 
    
    # title
    fig.text(0.05, 0.97,title_text, horizontalalignment='left', verticalalignment='top', fontsize=14,
             transform = ax.transAxes) # transAxes means text coords are normalized to full figure (not data)
    
    savefigname = savefigname + ".pdf" 
    application_window.setWindowTitle(savefigname) # set applivation window title
    #fig.savefig(savefigname, dpi=1200, transparent=True, pad_inches=0)
    #fig.canvas.set_window_title(savefigname) 
    #print "saved", savefigname
    if gp: gp.AddMessage("Saved " + savefigname)
    
    # Show interactive app for this winsize?
    #if winsize == interact_with: # this window size (1 means no smoothing) is interactive
        #plt.interactive(True)
        #if gp: gp.AddMessage("You should see the interactive plot for size " + str(winsize) + " - close it when you're ready to proceed")
        #plt.show() # will wait until used exits the app
        #print "going non-interacive"
        #plt.interactive(False)
        
    #plt.close("all") # cleanup (should be done to only this plot ...
    
    return fig 
        
    
    #raw_input("done - press any key to continue")
    
    #plt.polar()
    #plt.thetagrids((90,45,0,-45,-90), ("0", "45", "90", "135","180"))
    #plt.yticks([])
    
    #raster object via python in Arc10
    #http://help.arcgis.com/en/arcgisdesktop/10.0/help/index.html#//000v000000wt000000.htm
# end def plot()



class Qt4MplCanvas(FigureCanvas):
    """Class to represent the FigureCanvas widget"""
    def __init__(self, parent, fig):
        
        # store Figure
        #self.fig = Figure()
        self.fig = fig

        # initialization of the canvas
        FigureCanvas.__init__(self, self.fig)
        # set the parent widget
        self.setParent(parent)

        # we define the widget as expandable
        FigureCanvas.setSizePolicy(self,
                                   QtGui.QSizePolicy.Expanding,
                                   QtGui.QSizePolicy.Expanding)
        # notify the system of updated policy
        FigureCanvas.updateGeometry(self)

# SubClass nav bar to overwrite print_figure()
#from matplotlib.backends.backend_agg import FigureCanvasAgg
#class MyNavBar(NavigationToolbar):
    #def __init__(self, *args):
        #print "init my nav bar"
        #NavigationToolbar.__init__(self, *args)
        
    #def print_figure(self, *args, **kwargs):
        #print "IN MY NAVBAR'S PRINT_FIGURE"
        #FigureCanvasAgg.print_figure(self, *args, **kwargs)
        #self.draw()
        
class ApplicationWindow(QtGui.QMainWindow):

    def __init__(self):
        # initialization of Qt MainWindow widget
        QtGui.QMainWindow.__init__(self)
        # set window title
        self.setWindowTitle("Matplotlib Figure in a Qt4 Window With NavigationToolbar")

        # instantiate a widget, it will be the main one
        self.main_widget = QtGui.QWidget(self)
        self.status_text = QtGui.QLabel("")
        self.statusBar().addWidget(self.status_text, 1)
        
    def dostuff(self):
        print "toggle()",
        for rb in self.radio_buttons:
            if rb.isChecked():
                print "from", rb.Text()
        
    def add_Matplotlib_canvas_widget(self, fig):
        # instantiate our Matplotlib canvas widget
        self.qmc = Qt4MplCanvas(self.main_widget, fig)
        #fig.canvas.draw() # Needed before figure can be saved (?)

    def add_widgets(self, filter_window_sizes=None):
        
        # create a vertical box layout widget (for qmc and hbl)
        vbl = QtGui.QVBoxLayout(self.main_widget)
        
        # horizontal box
        hbl = QtGui.QHBoxLayout()
    
        # navigation toolbar
        self.ntb = NavigationToolbar(self.qmc, self.main_widget)
        #self.ntb = MyNavBar(self.qmc, self.main_widget) # my own, subclassed nav bar
        self.ntb.setMaximumWidth(420)
        self.ntb.setMinimumWidth(420)
        self.ntb.canvas.draw()
        
        #sizePolicy = QtGui.QSizePolicy(QtGui.QSizePolicy.Fixed, QtGui.QSizePolicy.Fixed)
        #self.ntb.setSizePolicy(sizePolicy)
        hbl.addWidget(self.ntb) # add nav bar to hz box, left aligned
        hbl.setAlignment(self.ntb, QtCore.Qt.AlignLeft)
        
        # Add radiobuttons for plotting at different window sizes
        #if len(filter_window_sizes) > 1:
            #spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
            #hbl.addItem(spacerItem)
            ## fill horz box with label and radiobuttons, right aligned
            #label = QtGui.QLabel("Filter window size:")
            ##label.setEnabled(False)
            ##self.connect(self.draw_button, QtCore.SIGNAL('clicked()'), self.buttonClickedCB)
            #hbl.addWidget(label) # add button to hz box
            #hbl.setAlignment(label, QtCore.Qt.AlignRight)
            #self.radio_buttons = []
            #for ws in filter_window_sizes:
                #print ws
                #rb = QtGui.QRadioButton()
                #rb.setChecked(False)
                #rb.setAutoExclusive(True)
                #rb.setText(str(ws))
                #rb.setObjectName(str(ws))
                #self.connect(rb, QtCore.SIGNAL("toggled()"), self.dostuff) # doens't work
                #hbl.addWidget(rb)
                #hbl.setAlignment(rb, QtCore.Qt.AlignRight)
                #self.radio_buttons.append(rb)
            #self.radio_buttons[0].setChecked(True) # switch first RB on
        # end if
  
        # pack qmz and hz box  into the vertical box
        vbl.addWidget(self.qmc)
        vbl.addLayout(hbl)
        
        self.main_widget.setLayout(vbl) # ?
        # set the focus on the main widget
        self.main_widget.setFocus()
        # set the central widget of MainWindow to main_widget
        self.setCentralWidget(self.main_widget)

#       
# MAIN
#

# pass in args from the ArcGIS frontend and the geoprocessor
def main(argv=None, gp=None):
    
    ### start here for editing configuration values by hand
    # make sure all lists have brackets [], NOT round parens () around them!
    
    #csv_folder = os.getcwd() + os.sep + "L1_test5" #  folder containing the csv files , os.getcwd() means current folder
    csv_folder = r"C:\temp\extractor\L1_test3"  # full path for folder, must start with r 
    
    csv_file_name = "fld_l1" # base name of csv file name, before the time step name  
    
    #csv_center_point_file = "#" # # means no user-given center point, assume perfect centering around a centroid
    #csv_center_point_file = r"C:\temp\extractor\center_point.shp" # full path given directly, mind the r
    csv_center_point_file = "center_point_coordinates.csv" # assumed to be inside csv_folder!
    csv_center_point_file = csv_folder + os.sep + csv_center_point_file # make full path
    
    
    time_steps = ["aug25", "sep02", "sep08"] # list of date/times (this is the order for the legend)
    #time_steps = ["aug25", "sep02"]

    #bearings = ["000", "020", "040", "060", "080", "100", "120", "140", "160"] # list of bearings, must be 0 padded strings!
    bearings = ["000", "030", "060", "090", "120", "150"]
    # This is the order of the subplots to zig-zag from upper left to lower right 
        
   
    # sizes for smoothing calculations that are done
    #window_sizes = [1, 3,5,7,15,25,51] # must include 1 for unsmoothed
    window_sizes = [1, 7, 25] 
    
    interact_with = 1 # which of the window_sizes is going to be shown at start?
    
    # graphics settings
    #transform = "log" #("none"=no transform "log"=logarithmic)
    transform = "none" # i.e. linear
    
    # spatial tiling setup of sub-plots,  string with "X x Y" e.g. "2 x 5" or "3 x 3" 
    sub_plot_setup = "3 x 2"  
    #sub_plot_setup = "4 x 2"
    #sub_plot_setup = "6 x 2"
    
    # Should all subuplots be linkes? True/False
    link_subplots = True
    #link_subplots = False
    
    # filder type and list of window sizes (uneven int) in addition to window 1 (unfiltered)
    filter_type = "hanning" # 'flat', 'hanning', 'hamming', 'bartlett', 'blackman'
    
    ####  End of editing configuration values by hand

    if gp: 
        #gp.AddMessage("in plot_in_qt4_navbar.py's main")
        gp.AddMessage(str(argv))
        pass

    # if we have args (from a GUI frontend), print them out and overwrite
    # the defaults with them
    args = sys.argv
    for i in range(1, len(args)):
        if gp: 
            #gp.AddMessage("Arg %i is %s" % (i, args[i])) # debug, show args from ArcGIS frontend
            pass
        else: print "Arg %i is %s" % (i, args[i])
        if i == 1: csv_folder = args[1]
        if i == 2: csv_file_name = args[2]
        if i == 3: 
            time_steps = args[3].split(';') # e.g."aug25;sep02;sep08"
            #if gp: gp.AddMessage(str(time_steps))
        if i == 4: 
            bearings = args[4].split(';') # e.g."000;020;040;060;080;100;120;140;160""
            #if gp: gp.AddMessage(str(bearings))
        
        # Optional:
        if i == 5: 
            if args[5] == "#": window_sizes = [] # no smothing
            else:
                window_sizes_str = args[5].split(';') # smoothing width e.g. "3;7;15;27"
                window_sizes = [ int(ws) for ws in window_sizes_str ] # convert to list of ints
        if i == 6: 
            interact_with_str = args[6] # Window size of the plot to bring up interactively:
                                    # #=none, 1=unsmoothed, 7=winsize of width 7
            if interact_with_str == "#": interact_with = 1 # # is ArcGIS' empty, default 1 (unfiltered)
            else: interact_with = int(interact_with_str)
        if i == 7: 
            transform = args[7]  #"none"=no transform, "log"=logarithmic
            if transform == "#": transform = "none"
        if i == 8: 
            sub_plot_setup = args[8] #string with "X x Y" e.g. "2 x 5" or "3 x 3" 
            if sub_plot_setup == "#": sub_plot_setup = "3 x 3"
        if i == 9: 
            filter_type = args[9]
            if filter_type == "#": filter_type == "hanning"
            
    # check if all needed csv files exist
    if gp: gp.AddMessage("Checking if requested csv files exist ...")
    for ts in time_steps:
        for br in bearings:
            f = csv_folder + os.sep + csv_file_name + "_" + ts + \
                "_" + br + ".csv" # assemble full path/filename for each csv file
            print f
            if os.path.exists(f):
                #print f, "exists"
                #if gp: gp.AddMessage("  OK for " + f)
                pass
            else:
                print "\nERROR:", f, "does NOT exist - exiting"
                if gp: gp.AddMessage("  " + f + " not found - exiting")
                sys.exit("Error " + f + " not found")
    print "csv files OK"
    if gp: gp.AddMessage("... csv files are OK")
    
    # returns a dict containing all profiles (raw and filtered)
    profiles = prepare_data(csv_folder,csv_file_name, csv_center_point_file, time_steps, 
                                         bearings,window_sizes, filter_type, transform, 
                                         gp) 
    
    # create the GUI application
    qApp = QtGui.QApplication(sys.argv)
    # instantiate and show the ApplicationWindow widget
    aw = ApplicationWindow()
   
    
    
    fig = plot(profiles,  interact_with, link_subplots, sub_plot_setup, aw, gp)
    aw.add_Matplotlib_canvas_widget(fig)
    aw.add_widgets(filter_window_sizes = window_sizes)
    

    # create Figure
   
    # make pdf file
    savefigname = str(aw.windowTitle())
    fig.savefig(savefigname, dpi=1200, transparent=True, pad_inches=0)
    #aw.statusBar().showMessage("saved " + savefigname)
    if gp: gp.AddMessage("saved " + savefigname)
   
    #aw.ntb.canvas.draw() # re-draw navbar 
    aw.show()
    ret = qApp.exec_() # execute qApp
    return ret

# call main and exit with its return
if __name__ == "__main__":
    
    # I'm using the exception here so that when this python script is 
    # called from a ArcGIS tool the console does not report a 
    # <type 'exceptions.SystemExit'> "error" when the script is done
    # http://gis.stackexchange.com/questions/1015/how-to-have-a-python-script-self-terminate-in-arcmap10
    try:
        sys.exit(main())
    except SystemExit:
        pass
    